/*
 * LTE Game Engine
 * Copyright (C) 2006-2008 SiberianSTAR <haxormail@gmail.com>
 * http://www.ltestudios.com
 *  
 * The LTE Game Engine is based on Irrlicht 1.0
 * Irrlicht Engine is Copyright (C) 2002-2006 Nikolaus Gebhardt
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
 
#include "CGUIEditBox.h"
#include "IGUISkin.h"
#include "IGUIEnvironment.h"
#include "IGUIFont.h"
#include "IVideoDriver.h"
#include "rect.h"
#include "os.h"
#include "Keycodes.h"

extern int key_state;
extern int key_type;

extern int wasStartDown;
namespace engine
{
namespace gui
{

//! constructor
CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment,
                         IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
                         IOSOperator* op)
	: IGUIEditBox(environment, parent, id, rectangle), Border(border),
	OverrideColorEnabled(false), OverrideFont(0), CursorPos(0), ScrollPos(0),
	MarkBegin(0), MarkEnd(0), MouseMarking(false), Operator(op), Max(0)
{
	#ifdef _DEBUG
	setDebugName("CGUIEditBox");
	#endif

	OverrideColor = video::SColor(101,255,255,255);
	Text = text;

	if (Operator)
		Operator->grab();
}


//! destructor
CGUIEditBox::~CGUIEditBox()
{
	if (OverrideFont)
		OverrideFont->drop();

	if (Operator)
		Operator->drop();
}


//! Sets another skin independent font.
void CGUIEditBox::setOverrideFont(IGUIFont* font)
{
	if (OverrideFont)
		OverrideFont->drop();

	OverrideFont = font;

	if (OverrideFont)
		OverrideFont->grab();
}


//! Sets another color for the text.
void CGUIEditBox::setOverrideColor(video::SColor color)
{
	OverrideColor = color;
	OverrideColorEnabled = true;
}


//! Sets if the text should use the overide color or the
//! color in the gui skin.
void CGUIEditBox::enableOverrideColor(bool enable)
{
	OverrideColorEnabled = enable;
}


//! called if an event happened.
bool CGUIEditBox::OnEvent(SEvent event)
{
	switch(event.EventType)
	{
	case EET_GUI_EVENT:
		if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
		{
			MouseMarking = false;
			MarkBegin = 0;
			MarkEnd = 0;
			return true;
		}
		break;
	case EET_KEY_INPUT_EVENT:
		return processKey(event);
		break;
	case EET_MOUSE_INPUT_EVENT:
		return processMouse(event);
		break;
	}

	return Parent ? Parent->OnEvent(event) : false;
}


bool CGUIEditBox::processKey(SEvent& event)
{
	if (event.KeyInput.PressedOnce)
	{
		if (event.KeyInput.Key == KEY_START && (wasStartDown == 0)) { key_type = 0; key_state = 1; }
	}
	if (!event.KeyInput.PressedDown)
		return false;

	// control shortcut handling

	if (event.KeyInput.Control)
	{
		switch(event.KeyInput.Key)
		{
		case KEY_KEY_C:
			// copy to clipboard
			if (Operator && MarkBegin != MarkEnd)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				core::stringc s;
				s = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(s.c_str());
			}
			break;
		case KEY_KEY_X:
			// cut to the clipboard
			if (Operator && MarkBegin != MarkEnd)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// copy
				core::stringc sc;
				sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
				Operator->copyToClipboard(sc.c_str());

				// delete
				core::stringw s;
				s = Text.subString(0, realmbgn);
				s.append( Text.subString(realmend, Text.size()-realmend) );
				Text = s;

				CursorPos = realmbgn;
				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		case KEY_KEY_V:
			// paste from the clipboard
			if (Operator)
			{
				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				// add new character
				c8* p = Operator->getTextFromClipboard();
				if (p)
				{
					if (MarkBegin == MarkEnd)
					{
						// insert text
						core::stringw s = Text.subString(0, CursorPos);
						s.append(p);
						s.append( Text.subString(CursorPos, Text.size()-CursorPos) );

						if (!Max || s.size()<=Max) // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos += s.size();
						}
					}
					else
					{
						// replace text

						core::stringw s = Text.subString(0, realmbgn);
						s.append(p);
						s.append( Text.subString(realmend, Text.size()-realmend) );

						if (!Max || s.size()<=Max)  // thx to Fish FH for fix
						{
							Text = s;
							s = p;
							CursorPos = realmbgn + s.size();
						}
					}
				}

				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		default:
			return false;
		}
	}

	// default keyboard handling

	if (!event.KeyInput.Control)
		switch(event.KeyInput.Key)
		{
		case KEY_END:
			if (event.KeyInput.Shift)
			{
				if (MarkBegin == MarkEnd)
					MarkBegin = CursorPos;
				MarkEnd = Text.size();
			}
			else
			{
				MarkBegin = 0;
				MarkEnd = 0;
			}
			CursorPos = Text.size();
			BlinkStartTime = os::Timer::getTime();
			break;
		case KEY_HOME:
			if (event.KeyInput.Shift)
			{
				if (MarkBegin == MarkEnd)
					MarkBegin = CursorPos;
				MarkEnd = 0;
			}
			else
			{
				MarkBegin = 0;
				MarkEnd = 0;
			}
			CursorPos = 0;
			BlinkStartTime = os::Timer::getTime();
			break;
		case KEY_RETURN:
		{
			SEvent e;
			e.EventType = EET_GUI_EVENT;
			e.GUIEvent.Caller = this;
			e.GUIEvent.EventType = EGET_EDITBOX_ENTER;
			Parent->OnEvent(e);
		}
			break;
		case KEY_LEFT:

			if (event.KeyInput.Shift)
			{
				if (CursorPos > 0)
				{
					if (MarkBegin == MarkEnd)
						MarkBegin = CursorPos;

					MarkEnd = CursorPos-1;
				}
			}
			else
			{
				MarkBegin = 0;
				MarkEnd = 0;
			}

			if (CursorPos > 0) CursorPos--;
			BlinkStartTime = os::Timer::getTime();
			break;

		case KEY_RIGHT:

			if (event.KeyInput.Shift)
			{
				if (Text.size() > CursorPos)
				{
					if (MarkBegin == MarkEnd)
						MarkBegin = CursorPos;

					MarkEnd = CursorPos+1;
				}
			}
			else
			{
				MarkBegin = 0;
				MarkEnd = 0;
			}

			if (Text.size() > CursorPos) CursorPos++;
			BlinkStartTime = os::Timer::getTime();
			break;

		case KEY_BACK:
			if (Text.size()!=0)
			{
				core::stringw s;

				if (MarkBegin != MarkEnd)
				{
					// delete marked text
					s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
					s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

					s = Text.subString(0, realmbgn);
					s.append( Text.subString(realmend, Text.size()-realmend) );
					Text = s;

					CursorPos = realmbgn;
				}
				else
				{
					// delete text behind cursor
					s = Text.subString(0, CursorPos-1);
					s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
					Text = s;
					--CursorPos;
				}

				if (CursorPos < 0)
					CursorPos = 0;
				BlinkStartTime = os::Timer::getTime();
				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		case KEY_DELETE:
			if (Text.size() != 0)
			{
				core::stringw s;

				if (MarkBegin != MarkEnd)
				{
					// delete marked text
					s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
					s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

					s = Text.subString(0, realmbgn);
					s.append( Text.subString(realmend, Text.size()-realmend) );
					Text = s;

					CursorPos = realmbgn;
				}
				else
				{
					// delete text before cursor
					s = Text.subString(0, CursorPos);
					s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
					Text = s;
				}

				if (CursorPos > Text.size())
					CursorPos = Text.size();

				BlinkStartTime = os::Timer::getTime();
				MarkBegin = 0;
				MarkEnd = 0;
			}
			break;
		default:
			if (event.KeyInput.Char != 0)
			{
				if (Text.size() < Max || Max == 0)
				{
					core::stringw s;

					if (MarkBegin != MarkEnd)
					{
						// replace marked text
						s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
						s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

						s = Text.subString(0, realmbgn);
						s.append(event.KeyInput.Char);
						s.append( Text.subString(realmend, Text.size()-realmend) );
						Text = s;
						CursorPos = realmbgn+1;
					}
					else
					{
						// add new character
						s = Text.subString(0, CursorPos);
						s.append(event.KeyInput.Char);
						s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
						Text = s;
						++CursorPos;
					}

					BlinkStartTime = os::Timer::getTime();
					MarkBegin = 0;
					MarkEnd = 0;
				}
			}
			break;
		}

	// calculate scrollpos

	IGUIFont* font = OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (!OverrideFont)
		font = skin->getFont();

	s32 cursorwidth = font->getDimension(L"_ ").Width;

	s32 minwidht = cursorwidth*2;
	if (minwidht >= AbsoluteRect.getWidth())
		minwidht = AbsoluteRect.getWidth() / 2;

	s32 tries = Text.size()*2;
	if (tries < 100)
		tries = 100;

	for (s32 t=0; t<tries; ++t)
	{
		core::stringw s = Text.subString(0,CursorPos);
		s32 charcursorpos = font->getDimension(s.c_str()).Width;

		s = Text.subString(0, ScrollPos);
		s32 charscrollpos = font->getDimension(s.c_str()).Width;

		if ((charcursorpos + cursorwidth - charscrollpos) > AbsoluteRect.getWidth())
			ScrollPos++;
		else
		if ((charcursorpos + cursorwidth - charscrollpos) < minwidht)
		{
			if (ScrollPos > 0)
				ScrollPos--;
			else
				break;
		}
		else
			break;
	}

	return true;
}

//! draws the element and its children
void CGUIEditBox::draw()
{
	if (!IsVisible)
		return;

	bool focus = Environment->hasFocus(this);

	IGUISkin* skin = Environment->getSkin();
	if (!skin)
		return;

	engine::video::IVideoDriver* driver = Environment->getVideoDriver();

	core::rect<s32> frameRect(AbsoluteRect);

	// draw the border

	if (Border)
	{
		skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
		                       false, true, frameRect, &AbsoluteClippingRect);

		frameRect.UpperLeftCorner.X += 3;
	}

	// draw the text

	IGUIFont* font = OverrideFont;
	if (!OverrideFont)
		font = skin->getFont();

	if (font)
	{
		// calculate cursor pos

		core::stringw s = Text.subString(0,CursorPos);
		s32 charcursorpos = font->getDimension(s.c_str()).Width;

		s = Text.subString(0, ScrollPos);
		s32 charscrollpos = font->getDimension(s.c_str()).Width;

		core::rect<s32> rct;

		// draw mark

		if (focus && MarkBegin != MarkEnd)
		{
			rct = frameRect;

			rct.LowerRightCorner.Y -= 2;
			rct.UpperLeftCorner.Y += 2;

			s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
			s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

			s = Text.subString(0, realmbgn);
			s32 mbegin = font->getDimension(s.c_str()).Width;

			s = Text.subString(realmbgn, realmend - realmbgn);
			s32 mend = font->getDimension(s.c_str()).Width;

			rct.UpperLeftCorner.X  += mbegin - charscrollpos;
			rct.LowerRightCorner.X = rct.UpperLeftCorner.X + mend;

			driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), rct, &AbsoluteClippingRect);
		}

		// draw cursor

		if (focus && (os::Timer::getTime() - BlinkStartTime) % 700 < 350)
		{
			rct = frameRect;
			rct.UpperLeftCorner.X += charcursorpos;
			rct.UpperLeftCorner.X -= charscrollpos;

			font->draw(L"_", rct,
			           OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
			           false, true, &AbsoluteClippingRect);
		}

		// draw text

		if (Text.size())
		{
			rct = frameRect;
			rct.UpperLeftCorner.X -= charscrollpos;

			if (focus && MarkBegin != MarkEnd)
			{
				// marked text

				font->draw(Text.c_str(), rct,
				           OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
				           false, true, &AbsoluteClippingRect);

				s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
				s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;

				s = Text.subString(0, realmbgn);
				s32 mbegin = font->getDimension(s.c_str()).Width;

				s = Text.subString(realmbgn, realmend - realmbgn);

				rct.UpperLeftCorner.X += mbegin;

				font->draw(s.c_str(), rct,
				           OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
				           false, true, &AbsoluteClippingRect);
			}
			else
			{
				// normal text
				font->draw(Text.c_str(), rct,
				           OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
				           false, true, &AbsoluteClippingRect);
			}
		}
	}
}

//! Sets the new caption of this element.
void CGUIEditBox::setText(const wchar_t* text)
{
	Text = text;
	CursorPos = 0;
	ScrollPos = 0;
	MarkBegin = 0;
	MarkEnd = 0;
}


//! Sets the maximum amount of characters which may be entered in the box.
//! \param max: Maximum amount of characters. If 0, the character amount is
//! infinity.
void CGUIEditBox::setMax(s32 max)
{
	Max = max;
	if (Max < 0)
		Max = 0;

	if (Text.size() > Max && Max != 0)
		Text = Text.subString(0, Max);
}


//! Returns maximum amount of characters, previously set by setMax();
s32 CGUIEditBox::getMax()
{
	return Max;
}


bool CGUIEditBox::processMouse(SEvent& event)
{
	switch(event.MouseInput.Event)
	{
	case engine::EMIE_LMOUSE_LEFT_UP:
		if (Environment->hasFocus(this))
		{
			CursorPos = getCursorPos(event.MouseInput.X);
			if (MouseMarking)
				MarkEnd = CursorPos;
			MouseMarking = false;
			return true;
		}
		break;
	case engine::EMIE_MOUSE_MOVED:
	{
		if (MouseMarking)
		{
			CursorPos = getCursorPos(event.MouseInput.X);
			MarkEnd = CursorPos;
			return true;
		}
	}
		break;
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (!Environment->hasFocus(this))
		{
			// get focus
			BlinkStartTime = os::Timer::getTime();
			Environment->setFocus(this);
			MouseMarking = true;
			CursorPos = getCursorPos(event.MouseInput.X);
			MarkBegin = CursorPos;
			MarkEnd = CursorPos;
			return true;
		}
		else
		{
			if (!AbsoluteRect.isPointInside(
			            core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)))
			{
				// remove focus
				Environment->removeFocus(this);
				return false;
			}

			// move cursor

			CursorPos = getCursorPos(event.MouseInput.X);

			if (!MouseMarking)
				MarkBegin = CursorPos;

			MouseMarking = true;
			MarkEnd = CursorPos;
			return true;
		}
	}

	return false;
}


s32 CGUIEditBox::getCursorPos(s32 x)
{
	IGUIFont* font = OverrideFont;
	IGUISkin* skin = Environment->getSkin();
	if (!OverrideFont)
		font = skin->getFont();

	core::stringw s = Text.subString(0, ScrollPos);
	s32 charscrollpos = font->getDimension(s.c_str()).Width;

	s32 idx = font->getCharacterFromPos(Text.c_str(), x - (AbsoluteRect.UpperLeftCorner.X + 3) + charscrollpos);
	if (idx != -1)
		return idx;

	return Text.size();
}


} // end namespace gui
} // end namespace engine

